package com.yeskery.nut.script.parser;

import com.yeskery.nut.script.Metadata;
import com.yeskery.nut.util.StringUtils;

import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

/**
 * 抽象脚本元数据读取器
 * @author sprout
 * 2022-05-13 17:42
 */
public abstract class AbstractScriptMetadataReader implements ScriptMetadataReader {

    /** 换行符 */
    public static final char NEW_LINE_SEPARATOR = '\n';

    /** 忽略的前缀长度 */
    private static final int IGNORE_LENGTH = 2;

    /** 脚本元数据 */
    private final ScriptMetadataParser scriptMetadataParser;

    /**
     * 抽象脚本元数据读取器构造方法
     * @param scriptMetadataParser 脚本元数据转换器
     */
    protected AbstractScriptMetadataReader(ScriptMetadataParser scriptMetadataParser) {
        this.scriptMetadataParser = scriptMetadataParser;
    }

    @Override
    public List<Metadata> parseMetadata() {
        String resource = getScriptResource();
        Stack<MetadataIndex> stack = new Stack<>();
        List<Metadata> metadataList = new LinkedList<>();
        for (int i = 0; i < resource.length(); i++) {
            if (resource.charAt(i) == SCRIPT_START_SYMBOL) {
                stack.push(new MetadataIndex(i, SCRIPT_START_SYMBOL));
            } else if (resource.charAt(i) == SCRIPT_END_SYMBOL) {
                if (stack.empty()) {
                    throw new ScriptParseException(i, getNearbyContent(resource, i, 5));
                } else if (stack.size() > 1) {
                    stack.pop();
                } else if (stack.size() == 1) {
                    MetadataIndex metadataIndex = stack.pop();
                    String script = resource.substring(metadataIndex.getIndex(), i + 1);
                    String formatScriptResource = formatScriptResource(script);
                    if (formatScriptResource != null) {
                        Metadata metadata = scriptMetadataParser.parseMetadata(formatScriptResource);
                        if (metadata != Metadata.EMPTY_METADATA) {
                            metadataList.add(metadata);
                        }
                    }
                }
            }
        }
        if (!stack.empty()) {
            throw new ScriptParseException(resource.length() - 1, getNearbyContent(resource, resource.length() - 1, 5));
        }
        return metadataList;
    }

    /**
     * 获取脚本字符串
     * @return 脚本字符串
     */
    protected abstract String getScriptResource();

    /**
     * 格式化脚本字符串
     * @param script 脚本字符串
     * @return 格式化后的脚本字符串
     */
    private static String formatScriptResource(String script) {
        StringBuilder stringBuilder = new StringBuilder(script);
        if (stringBuilder.substring(1, stringBuilder.length()).trim().isEmpty()) {
            return null;
        }
        if (stringBuilder.charAt(1) != NEW_LINE_SEPARATOR) {
            stringBuilder.insert(1, NEW_LINE_SEPARATOR);
        }
        for (int i = stringBuilder.length() - IGNORE_LENGTH; i >= 0; i--) {
            char c = stringBuilder.charAt(i);
            if (c == ';') {
                stringBuilder.deleteCharAt(i);
                break;
            } else if (c != '\r' && c != '\n' && c != '\t' && c != '\f' && c != ' ') {
                break;
            }
        }
        if (stringBuilder.charAt(stringBuilder.length() - IGNORE_LENGTH) != NEW_LINE_SEPARATOR) {
            stringBuilder.insert(stringBuilder.length() - 1, NEW_LINE_SEPARATOR);
        }
        return stringBuilder.toString().replaceAll(";[\\s|\\n]*path", "\npath")
                .replaceAll(";[\\s|\\n]*method[\\s|\\n]*", "\nmethod")
                .replaceAll(";[\\s|\\n]*response_header", "\nresponse_header")
                .replaceAll(";[\\s|\\n]*body", "\nbody")
                .replaceAll("\\n+\\[", "\n[")
                .replaceAll("\\n+]", "\n]");
    }

    /**
     * 获取字符串附近文本
     * @param resource 字符串
     * @param index 索引
     * @return 字符串附近文本
     */
    private String getNearbyContent(String resource, int index, int size) {
        if (StringUtils.isEmpty(resource) || index < 0 || size < 0) {
            return resource;
        }
        if (size == 0) {
            return "";
        }
        int length = resource.length();
        if (index < size) {
            return resource.substring(0, Math.min(length, size));
        } else if (index >= length - size - 1) {
            return resource.substring(Math.max(0, length - size), Math.min(length, index + size + 1));
        }
        int half = size / 2;
        return resource.substring(index - half, Math.min(length, index + size - half));
    }
}
